<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>CSV 图片渲染工具</title>
  <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
  <script src="https://unpkg.com/papaparse@5.4.1/papaparse.min.js"></script>
  <script src="https://unpkg.com/jschardet@3.0.0/dist/jschardet.min.js"></script>
  <style>
    :root{
      --bg:#ffffff; --card:#f9f9f9; --muted:#666; --ring:#ddd; --text:#111; --accent:#2f81f7;
      --ok:#16a34a; --bad:#ef4444;
    }
    *{box-sizing:border-box}
    body{margin:0;background:var(--bg);color:var(--text);font:14px/1.4 system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial;height:100vh;display:flex;flex-direction:column}
    .topbar{display:flex;align-items:center;gap:12px;padding:14px 16px;border-bottom:1px solid var(--ring);position:sticky;top:0;background:var(--bg);z-index:10}
    .btn{appearance:none;border:1px solid var(--ring);background:var(--card);color:var(--text);padding:10px 14px;border-radius:12px;cursor:pointer}
    .btn:hover{border-color:#bbb}
    .hidden{display:none}
    .q-input{flex:1;position:relative}
    .q-input input{width:100%;border:1px solid var(--ring);background:var(--card);color:var(--text);padding:10px 14px;border-radius:12px;outline:none}
    .pill{min-width:120px;text-align:center;border:1px solid var(--ring);background:var(--card);padding:10px 14px;border-radius:12px}
    .dropdown{position:absolute;top:46px;left:0;right:0;background:var(--card);border:1px solid var(--ring);border-radius:12px;max-height:260px;overflow:auto;box-shadow:0 10px 30px rgba(0,0,0,.15)}
    .dropdown button{display:block;width:100%;text-align:left;background:none;border:0;color:var(--text);padding:10px 12px;cursor:pointer}
    .dropdown button:hover{background:#eee}

    .grid{flex:1;display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;padding:16px;overflow:hidden}
    .col{background:var(--card);border:1px solid var(--ring);border-radius:16px;display:flex;flex-direction:column;min-height:0}
    .col h3{margin:0;padding:14px 16px;border-bottom:1px solid var(--ring);text-align:center;flex:0 0 auto}
    .list{padding:16px;display:grid;grid-template-columns:1fr;gap:14px;overflow:auto;flex:1}
    .img-card{border:1px solid var(--ring);border-radius:12px;padding:8px;position:relative}
    .img-card img{width:100%;height:auto;display:block;border-radius:8px;max-width:100%;max-height:600px;object-fit:contain}
    .error{padding:40px 10px;text-align:center;color:var(--bad);font-weight:600}
    .meta{display:flex;align-items:center;justify-content:flex-start;color:var(--muted);font-size:12px;margin-top:6px}

    @media (max-width: 1100px){.grid{grid-template-columns:1fr;}}
  </style>
</head>
<body>
  <div id="app" style="flex:1;display:flex;flex-direction:column;min-height:0">
    <div class="topbar">
      <label class="btn" for="csv">上传 CSV</label>
      <input id="csv" class="hidden" type="file" accept=".csv,text/csv" @change="onFile" />

      <div class="q-input" v-if="qList.length">
        <input v-model.trim="search" placeholder="搜索题目编号（支持模糊搜索）" @focus="open=true" @input="open=true" @keydown.escape="open=false"/>
        <div class="dropdown" v-if="open">
          <button v-for="q in filtered" :key="q" @click="selectQ(q)">{{ q }}</button>
          <div v-if="!filtered.length" style="padding:12px;color:var(--muted)">没有结果</div>
        </div>
      </div>

      <div class="pill" v-if="current">{{ current.题目编号 || '未选择' }}</div>
    </div>

    <div v-if="!rows.length" style="padding:24px;color:var(--muted)">请先上传 CSV 文件。表头至少包含：题目编号、输入图片链接、答案图片链接、负例图片链接。</div>

    <div v-else class="grid">
      <section class="col">
        <h3>输入图片</h3>
        <div class="list">
          <div class="img-card" v-for="(item,i) in display.input" :key="'in-'+i">
            <img v-if="!item.error" :src="item.url" loading="lazy" width="1920"/>
            <div class="error" v-else>{{ item.url }}</div>
            <div class="meta"><span>{{ i+1 }}</span></div>
          </div>
        </div>
      </section>

      <section class="col">
        <h3>答案图片</h3>
        <div class="list">
          <div class="img-card" v-for="(item,i) in display.answer" :key="'ans-'+i">
            <img v-if="!item.error" :src="item.url" loading="lazy" width="1920"/>
            <div class="error" v-else>{{ item.url }}</div>
            <div class="meta"><span>{{ i+1 }}</span></div>
          </div>
        </div>
      </section>

      <section class="col">
        <h3>负例图片</h3>
        <div class="list">
          <div class="img-card" v-for="(item,i) in display.negative" :key="'neg-'+i">
            <img v-if="!item.error" :src="item.url" loading="lazy" width="1920"/>
            <div class="error" v-else>{{ item.url }}</div>
            <div class="meta"><span>{{ i+1 }}</span></div>
          </div>
        </div>
      </section>
    </div>
  </div>

  <script>
    const { createApp, reactive, computed } = Vue

    createApp({
      setup(){
        const state = reactive({
          headers: [],
          rows: [],
          current: null,
          qList: [],
          search: '',
          open: false,
        })

        function normalizeUrl(u){
          if(!u) return ''
          return String(u).trim()
        }

        function splitUrls(cell){
          if(!cell) return []
          return String(cell)
            .split(';')
            .map(s => normalizeUrl(s))
            .filter(Boolean)
        }

        function buildDisplay(row){
          const input = splitUrls(row['输入图片链接']).map(url => ({url, loaded:false, error:false}))
          const answer = splitUrls(row['答案图片链接']).map(url => ({url, loaded:false, error:false}))
          const negative = splitUrls(row['负例图片链接']).map(url => ({url, loaded:false, error:false}))
          return { input, answer, negative }
        }

        function onFile(e){
          const file = e.target.files[0]
          if(!file) return
          Papa.parse(file, {
            header:true,
            skipEmptyLines:true,
            encoding: 'utf-8',
            complete: (res)=>{
              state.rows = res.data
              state.headers = res.meta && res.meta.fields ? res.meta.fields : Object.keys(res.data[0]||{})
              state.qList = state.rows.map(r => r['题目编号']).filter(Boolean)
              state.open = false
              state.search = ''
              state.current = state.rows[0] || null
            }
          })
        }

        function selectQ(q){
          const idx = state.rows.findIndex(r => (r['题目编号']||'').trim() === q.trim())
          if(idx >= 0){
            state.current = state.rows[idx]
          }
          state.search = q
          state.open = false
          window.scrollTo({top:0, behavior:'smooth'})
        }

        const filtered = computed(()=>{
          const kw = state.search.trim().toLowerCase()
          if(!kw) return state.qList
          return state.qList.filter(q => String(q).toLowerCase().includes(kw))
        })

        const display = computed(()=> state.current ? buildDisplay(state.current) : {input:[],answer:[],negative:[]})

        return { ...Vue.toRefs(state), onFile, selectQ, filtered, display }
      }
    }).mount('#app')
  </script>
</body>
</html>
